﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.SqlClient;

using jade.core;
using jade.core.behaviours;
using jade.lang.acl;
using Newtonsoft.Json;

namespace CMS1 {
    public class Scheduler : Agent {
        public const int SCHEDULED_OUTGOING_MESSAGE = 1001;//"SCHEDULED_MESSAGE";

        public const int PHASE_TRANSMISSION = 1002;

        public const int SCHEDULE_REQUEST = 1003;//"SCHEDULE";

        //public const int UNSOLVED_SCHEDULING_CONFLICT = 1004;

        //public const int WITHDRAW_PROCESSING_RESULT = 1005;

        //public const int PROCESS_CONFILCTS_REQUEST = 1006;//"CONFLICTS";

        public const int STATE_DETAILS = 1007;

        public const int RECEPTION_SCHEDULING = 1008;

        public DatabaseWrapper db {
            get;
            set;
        }
        public override void setup() {
            db = new DatabaseWrapper();
            db.initConnection(DatabaseWrapper.connString);
            //addBehaviour(new ReceiveExternalMsgs());
            ParallelBehaviour timeCheckBehaviours = new ParallelBehaviour(ParallelBehaviour.WHEN_ALL);
            DataStore ds = timeCheckBehaviours.getDataStore();
            CheckDialyTasks cdt = new CheckDialyTasks(this, 1);
            CheckHourlyTasks cht = new CheckHourlyTasks(this, 1);
            cdt.setDataStore(ds);
            cht.setDataStore(ds);
            timeCheckBehaviours.addSubBehaviour(cdt);
            timeCheckBehaviours.addSubBehaviour(cht);
        }

        /*************************************************
         * Receive External
         * **********************************************/
        class ReceiveExternalMsgs : CyclicBehaviour {
            MessageTemplate externalMsg;
            public ReceiveExternalMsgs() {
                externalMsg = MessageTemplate.MatchSender(new AID("GatewayAgent", AID.ISLOCALNAME));
            }
            public override void action() {
                ACLMessage aclMsg = myAgent.receive(externalMsg);
                if (aclMsg != null) {
                    //string[] msgElements = msg.getContent().Split(new char[] { ';' });
                    //string purpose = msgElements[0];
                    Message msg = JsonConvert.DeserializeObject<Message>(aclMsg.getContent());
                    switch (msg.Purpose) {
                        case SCHEDULE_REQUEST:
                            break;
                    }
                } else {
                    block();
                }
            }
        }

        /*************************************************
         * Receive Internal
         * **********************************************/
        class ReceiveInternalMsgs : CyclicBehaviour {
            MessageTemplate internalMsg;
            public ReceiveInternalMsgs() {
                //MessageTemplate fromMonitor = MessageTemplate.MatchSender(new AID("Monitor", AID.ISLOCALNAME));
                internalMsg = MessageTemplate.MatchSender(new AID("Reporter", AID.ISLOCALNAME));
                //internalMsg = MessageTemplate.or(fromMonitor, fromReporter);
            }
            public override void action() {
                ACLMessage aclMsg = myAgent.receive(internalMsg);
                if (aclMsg != null) {
                    Message msg = JsonConvert.DeserializeObject<Message>(aclMsg.getContent());
                    switch (msg.Purpose) {
                        case STATE_DETAILS:
                            //msg.Purpose = STATE_DETAILS;
                            break;
                        /*case PROCESS_CONFILCTS_REQUEST:
                            break;
                        case Reporter.SUBMISSION_WITHDRAW:
                            handleWithdraw();
                            break;*/
                    }
                } else {
                    block();
                }
            }

            /*private void handleWithdraw() {
                throw new NotImplementedException();
            }*/
        }


        /*************************************************
         * Hourly Checks
         * **********************************************/
        class CheckHourlyTasks : TickerBehaviour {
            DatabaseWrapper db;
            List<SMS> receptionSMS;
            public CheckHourlyTasks(Agent agent, long tickHours)
                : base(agent, tickHours * 5 * 1000) { // * 60 * 60 * 1000
                this.db = ((Scheduler)agent).db;
            }

            public override void onTick() {
                checkOutgoingMessages();
                checkSMS();
            }

            private void checkSMS() {
                if (receptionSMS == null) {
                    receptionSMS = (List<SMS>)getDataStore().get(CheckDialyTasks.RECEPTION_REMINDING_SMS);
                    if (receptionSMS == null) {
                        return;
                    }
                }
                DateTime now = DateTime.Now;
                foreach (SMS sms in receptionSMS) {
                    if (now.Date == sms.SendDate.Date) {
                        if ((now.Hour - sms.SendDate.Hour) <= 2) {
                            sendSMS(sms);
                            receptionSMS.Remove(sms);
                        }
                    }
                }
            }

            private void sendSMS(SMS sms) {
            }

            private void checkOutgoingMessages() {
                int[] outgoingMessagesIDs = (int[])db.selectColumn("id").from("Outgoing_message").
                    where(GatewayAgent.myConf).
                    and("CONVERT(date,send_date,110) = (CONVERT(date,GETDATE(),110))").
                    and("DATEPART(hh, send_date) = DATEPART(hh, GETDATE())").execute();

                /*object[][] outgoingMessages = (object[][])db.select("id").from("Outgoing_message").
                    where(GatewayAgent.myConf).
                    and("CONVERT(date,send_date,110) = (CONVERT(date,GETDATE(),110))").
                    and("DATEPART(hh, send_date) = DATEPART(hh, GETDATE())").execute();
                string messagesIDs = "";
                if (outgoingMessages != null) {
                    for (int i = 0; i < outgoingMessages.Length; i++) {
                        messagesIDs += outgoingMessages[i][0] + ",";
                    }
                    messagesIDs = messagesIDs.Substring(0, messagesIDs.Length - 1);*/
                ACLMessage aclMessage = new ACLMessage(ACLMessage.REQUEST);
                aclMessage.addReceiver(new AID("Communicator", AID.ISLOCALNAME));
                Message msg = new Message();
                msg.Purpose = SCHEDULED_OUTGOING_MESSAGE;
                //msg.Data = messagesIDs;
                msg.Data = JsonConvert.SerializeObject(outgoingMessagesIDs);
                aclMessage.setContent(JsonConvert.SerializeObject(msg));
                myAgent.send(aclMessage);
            }
        }
    }

    /*************************************************
     * Dialy Checks
     * **********************************************/
    class CheckDialyTasks : TickerBehaviour {
        DatabaseWrapper db;
        int currentPhaseID = -1;
        List<SMS> receptionSMS;
        public const string RECEPTION_REMINDING_SMS = "reception_reminding_sms";
        public CheckDialyTasks(Agent agent, long tickDays)
            : base(agent, tickDays * 5 * 1000) { // * 60 * 60 * 24 * 1000
            this.db = ((Scheduler)agent).db;
        }

        public override void onTick() {
            phaseTransmission();
            if (currentPhaseID > -1) {
                phaseTasks();
            }
        }
        private void phaseTransmission() {
            object[][] phases = (object[][])db.select("id, start_date").from("Phase").where(GatewayAgent.myConf).execute();
            if (phases != null) {
                for (int i = 0; i < phases.Length; i++) {
                    DateTime start_date = (DateTime)(phases[i][1]);
                    DateTime now = DateTime.Now;
                    if (now.Date.Equals(start_date.Date)) {
                        if (i > 0) { // no report at the beginning of the first phase
                            System.Diagnostics.Debug.WriteLine("Phase transmission detected..");
                            ACLMessage aclMessage = new ACLMessage(ACLMessage.INFORM);
                            aclMessage.addReceiver(new AID("Reporter", AID.ISLOCALNAME));
                            Message msg = new Message();
                            msg.Purpose = Scheduler.PHASE_TRANSMISSION;

                            List<int> phaseTasks = new List<int>();
                            string[] tasks = (string[])db.selectColumn("Task.name").from("Task, Phase_tasks").
                                where("Phase_tasks.phase_id='" + phases[i - 1][0] + "' and Phase_tasks.task_id=Task.id").and(GatewayAgent.myConf).execute();
                            foreach (string task in tasks) {
                                if (task.Equals("Invite/Call for Speakers/Papers") || task.Equals("Contact mass media")) {
                                    phaseTasks.Add(Communicator.INVITATION_RESULT | Scheduler.PHASE_TRANSMISSION);
                                } else if (task.Equals("Arrange residence") || task.Equals("Arrange cater")) {
                                    phaseTasks.Add(Communicator.DEALING_RESULT | Scheduler.PHASE_TRANSMISSION);
                                }
                            }
                            string[] details = { JsonConvert.SerializeObject(phaseTasks), "Phase No." + i + " Report" };

                            msg.Data = JsonConvert.SerializeObject(details);
                            aclMessage.setContent(JsonConvert.SerializeObject(msg));
                            myAgent.send(aclMessage);
                        }
                        // trasmission procedures **phase[i-1]
                        currentPhaseID = (int)phases[i][0];
                    }
                }
            }
            //return -1;
        }
        private void phaseTasks() {
            object[][] tasks = (object[][])db.select("name, start_date, length, chair_id").from("Task, Phase_tasks").
                where("Phase_tasks.phase_id='" + currentPhaseID + "' and Phase_tasks.task_id=Task.id").
                and("CONVERT(date,start_date,110) = (CONVERT(date,GETDATE(),110))").
                and(GatewayAgent.myConf).execute();
            if (tasks != null) {
                foreach (object[] task in tasks) {
                    //for (int i = 0; i < task.Length; i++) {
                    string name = (string)task[0];
                    DateTime now = DateTime.Now.Date;
                    DateTime startDate = (DateTime)task[1];
                    DateTime endDate = startDate.AddDays((int)task[2]);
                    if (name.Equals("Manage reception") && now.Equals(startDate)) {
                        scheduleReceptionDuties();
                    } else if (name.Equals("Evaluate submission") && now.Equals(endDate)) {
                        // end of evaluation
                        // set elected sessions
                    } else {
                        // procedures insert in interal message where chair_id='row[2]'
                        db.insert_into("Internal_message").columns("sender_id, receiver_id, date, title, message_content, state, conf_id").
                            values("'" + (int)task[3] + "', '" + (int)task[3] + "', '" + DateTime.Now +
                                   "', 'Begin of Task " + name +
                                   "', 'Today is the start of task \"" + name + "\" which lasts for " + (int)task[2] + " day(s)', '0', '" + GatewayAgent.CONF_ID + "'").execute();
                        //columns("name, email").values("'me', 'me@live.com'").execute();
                    }

                    System.Diagnostics.Debug.WriteLine("Task start detected");
                    //}
                }
            }
        }

        private void scheduleReceptionDuties() {
            List<ReceptionStaff> staff = new List<ReceptionStaff>();
            List<Attendee> unassignedAttendees = new List<Attendee>();
            object[][] staffDetails = (object[][])db.select("id, name, phone_number").from("Reception_staff").
                where(GatewayAgent.myConf).execute();
            foreach (object[] staffMemberDetails in staffDetails) {
                ReceptionStaff member = new ReceptionStaff();
                member.ID = (int)staffMemberDetails[0];
                member.Name = (string)staffMemberDetails[1];
                member.PhoneNumber = (int)staffMemberDetails[2];
                staff.Add(member);
            }

            DateTime firstArrivalDate = (DateTime)db.selectCell("MIN(arrival_datetime)").from("Attendee_arrival_details").
                where(GatewayAgent.myConf).execute();
            DateTime lastDayInConf = (DateTime)db.selectCell("end_date").from("Conference").where(GatewayAgent.myConf).execute();
            DateTime currentDate = firstArrivalDate.Date;
            int i = 0, k = 0;
            while (currentDate <= lastDayInConf) {
                List<Attendee> attendees = getAttendeesOfDay(currentDate);
                foreach (Attendee attendee in attendees) {
                    if (staff[i].isFreeAtTime(attendee.ArrivalDate)) {
                        staff[i].associateAttendee(attendee);
                        i++;
                        if (i == staff.Count) {
                            i = 0;
                        }
                    } else {
                        bool associated = false;
                        for (; k < staff.Count; k++) {
                            if (staff[k] != staff[i]) {
                                if (staff[k].isFreeAtTime(attendee.ArrivalDate)) {
                                    staff[k].associateAttendee(attendee);
                                    associated = true;
                                    k++;
                                    break;
                                }
                            }
                        }
                        if (!associated) {
                            unassignedAttendees.Add(attendee);
                        }
                        if (k == staff.Count) {
                            k = 0;
                        }
                    }
                }
                currentDate = currentDate.AddDays(1);
            }
            receptionSMS = new List<SMS>();
            List<List<string>> reception = new List<List<string>>();
            foreach (ReceptionStaff member in staff) {
                List<string> receptionDetails = new List<string>();
                receptionDetails.Add(member.Name);
                foreach (Attendee attendee in member.AssociatedAttendees) {
                    SMS sms = new SMS();
                    sms.SendDate = attendee.ArrivalDate.Date.AddHours(-1);
                    sms.Text = "Meet:\n" + attendee.Name + " (" + attendee.Position + ") At " + attendee.ArrivalPlace + ", " + attendee.ArrivalDate;
                    sms.PhoneNumber = member.PhoneNumber;
                    receptionSMS.Add(sms);
                    db.insert_into("Reception_staff_assignment").
                        columns("reception_staff_id, staff_member_name, attendee_id, attendee_name, reception_time, conf_id").
                        values("'" + member.ID + "', '" + member.Name + "', '" +
                        attendee.ID + "', '" + attendee.Name + "', '" + attendee.ArrivalDate + "', '" + GatewayAgent.CONF_ID + "'").execute();
                    receptionDetails.Add(attendee.Name);
                    receptionDetails.Add(attendee.ArrivalDate + "");
                }
                reception.Add(receptionDetails);

                //string text = "Meet:\n";
                for (int m = 0; m < member.AssociatedAttendees.Count; m++) {
                    Attendee attendee = member.AssociatedAttendees[m];
                    //if(attendee.ArrivalDate
                }
            }
            getDataStore().put(RECEPTION_REMINDING_SMS, receptionSMS);
            // generate report
            string[] report = new string[2];
            report[0] = JsonConvert.SerializeObject(reception);
            report[1] = JsonConvert.SerializeObject(unassignedAttendees);
            Message msg = new Message();
            msg.Purpose = Scheduler.RECEPTION_SCHEDULING;
            msg.Data = JsonConvert.SerializeObject(report);
            ACLMessage aclMessage = new ACLMessage(ACLMessage.INFORM);
            aclMessage.addReceiver(new AID("Reporter", AID.ISLOCALNAME));
            aclMessage.setContent(JsonConvert.SerializeObject(msg));
            myAgent.send(aclMessage);

        }
        private List<Attendee> getAttendeesOfDay(DateTime currentDate) {
            List<Attendee> attendees = new List<Attendee>();
            object[][] results = (object[][])db.select("attendee_id, name, position, arrival_place, arrival_datetime").from("Attendee, Attendee_arrival_details").
                where("Attendee.id=attendee_id").and(GatewayAgent.myConf).execute();
            foreach (object[] result in results) {
                if (((DateTime)result[2]).Date == currentDate.Date) {
                    Attendee attendee = new Attendee();
                    attendee.ID = (int)result[0];
                    attendee.Name = (string)result[1];
                    attendee.Position = (string)result[2];
                    attendee.ArrivalPlace = (string)result[3];
                    attendee.ArrivalDate = (DateTime)result[4];
                    attendees.Add(attendee);
                }
            }
            return attendees;
        }
    }
    public class SMS {
        public int PhoneNumber {
            get;
            set;
        }
        public DateTime SendDate {
            get;
            set;
        }
        public string Text {
            get;
            set;
        }
    }
    public class ReceptionStaff {
        public ReceptionStaff() {
            AssociatedAttendees = new List<Attendee>();
            TimeSlots = new bool[48];
        }
        public int ID {
            get;
            set;
        }
        public string Name {
            get;
            set;
        }
        public int PhoneNumber {
            get;
            set;
        }
        public List<Attendee> AssociatedAttendees {
            get;
            set;
        }
        public bool[] TimeSlots {
            get;
            set;
        }
        public int[] getTimeSlotIndices(DateTime datetime) {
            int index = datetime.Hour * 2;
            if (datetime.Minute == 0) {
                return new int[] { index, index + 1 };
            } else if (datetime.Minute < 30) {
                return new int[] { index, index + 1, (index + 2 > 47) ? index + 2 - 48 : index + 2 };
            } else if (datetime.Minute == 30) {
                return new int[] { index + 1, (index + 2 > 47) ? index + 2 - 48 : index + 2 };
            } else {
                return new int[] { index + 1, (index + 2 > 47) ? index + 2 - 48 : index + 2, (index + 3 > 47) ? index + 3 - 48 : index + 3 };
            }
        }
        public bool isFreeAtTime(DateTime datetime) {
            int[] indices = getTimeSlotIndices(datetime);
            foreach (int i in indices) {
                if (TimeSlots[i]) {
                    return false;
                }
            }
            return true;
            /*int index = datetime.Hour * 2;
            if (datetime.Minute == 0) {
                if (!TimeSlots[index] && !TimeSlots[index + 1]) {
                    return true;
                }
            } else if (datetime.Minute < 30) {
                if (!TimeSlots[index] && !TimeSlots[index + 1] && !TimeSlots[index + 2]) {
                    return true;
                }
            } else if (datetime.Minute == 30) {
                if (!TimeSlots[index + 1] && !TimeSlots[index + 2]) {
                    return true;
                }
            } else {
                if (!TimeSlots[index + 1] && !TimeSlots[index + 2] && !TimeSlots[3]) {
                    return true;
                }
            }
            return false;*/
        }
        /*public string AssociatedAttendeeName {
            get;
            set;
        }
        public DateTime ReceptionTime {
            get;
            set;
        }*/

        public void associateAttendee(Attendee attendee) {
            AssociatedAttendees.Add(attendee);
            int[] indices = getTimeSlotIndices(attendee.ArrivalDate);
            foreach (int index in indices) {
                TimeSlots[index] = true;
            }
        }
    }
    public class Attendee {
        public int ID {
            get;
            set;
        }
        public string Name {
            get;
            set;
        }
        public string Position {
            get;
            set;
        }
        public string ArrivalPlace {
            get;
            set;
        }
        public DateTime ArrivalDate {
            get;
            set;
        }
    }
}

//int[] iMessagesIDs = Array.ConvertAll(messagesIDs, int.Parse);

//var intArray = strArray.Select(int.Parse).ToArray();

//string formatted = date.ToString("dd-MM-yyyy");

//System.Diagnostics.Debug.WriteLine("Sql==> " + phases[i][0]);
//System.Diagnostics.Debug.WriteLine("@@@"+now);//start_date.Date.ToShortDateString());

/*ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
msg.addReceiver(new AID("Dummy", AID.ISLOCALNAME));
msg.setContent("2"+phases.ToString());
myAgent.send(msg);*/